package com.lancoo.ccas53.arrangecourse.dataprocess;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;
import com.lancoo.ccas53.arrangecourse.common.BinaryUtil;
import com.lancoo.ccas53.arrangecourse.entities.TeachingClassUnit;
import com.lancoo.ccas53.arrangecourse.entities.TimeslotRoom;
import com.lancoo.ccas53.pojo.dto.ArrangeTeachingClassDto;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @Description 生成排课教学班单元数据
 * @Author Fortysun
 * @Date 2023/5/16 17:50
 **/
@Slf4j
public class GenerateSchedulingData {

    /**
     * 将拆班课程生成的教学班，转换成教学班排课单元
     * 拆班的教学班，连上节次都是1，只需要按照周学时，拆成周学时数量的排课单元即可
     * 比如：周学时是3，那只需要拆成3个排课单元即可
     * @param taskId 排课计划id
     * @param teachingClassList 需要转换的教学班列表信息
     * @param startPos 教学班排课单元生成的起始位置
     * @return
     */
    public LinkedList<TeachingClassUnit> convertToSplitTeachingClassUnit(Integer taskId,
                                                                     List<ArrangeTeachingClassDto> teachingClassList,
                                                                     Integer startPos) {
        if (teachingClassList != null && !teachingClassList.isEmpty()) {
            //用于存放排课单元数据
            LinkedList<TeachingClassUnit> teachingclassUnitList = new LinkedList<>();
            int teachingClassUnitIndex = startPos;
            for (ArrangeTeachingClassDto teachingClass : teachingClassList) {
                //获取教学班的某个授课类型的周学时数量
                Integer weekNum = teachingClass.getWeekNum();
                //过滤掉脏数据，有些教学班没有设置周学时的话，直接跳过
                if (weekNum != null) {
                    //连上次数为0，默认就按照连上节次是1节，去拆分生成教学班排课单元
                    teachingClassUnitIndex = connectNumberIsZero(teachingClass, weekNum,
                            taskId, teachingClassUnitIndex, teachingclassUnitList);
                }
            }

            return teachingclassUnitList;
        }
        log.info("GenerateSchedulingData--convertToSplitTeachingClassUnit: 没有需要转换成教学班单元的教学班信息~！！");
        //不需要排课，返回空
        return null;
    }

    /**
     * 连上次数为0，默认就按照连上节次是1节，去拆分生成教学班排课单元
     * @param teachingClass 教学班对象
     * @param weekNum 周学时
     * @param taskId 计划id
     * @param teachingClassUnitIndex 生成的教学班排课单元id
     * @param teachingclassUnitList 用来存储的教学班排课单元列表信息
     * @return
     */
    private int connectNumberIsZero(ArrangeTeachingClassDto teachingClass,
                                    Integer weekNum,
                                    Integer taskId,
                                    int teachingClassUnitIndex,
                                    LinkedList<TeachingClassUnit> teachingclassUnitList) {
        //通过周学时数量，生成指定数量的教学班排课单元
        for (int i = 0; i < weekNum; i++) {
            //计算教学班排课单元的id
            teachingClassUnitIndex++;
            //生成排课教学班
            TeachingClassUnit teachingClassUnit = generateTeachingClassUnit(teachingClass,
                    teachingClassUnitIndex,
                    taskId,
                    teachingClass.getWeek());
            //将教学班的连上节次设为1
            teachingClassUnit.setConnectSection(1);
            //将教学班的连上次数设为0
            teachingClassUnit.setConnectNumber(0);
            //将生成的排课教学班，添加到返回的列表中
            teachingclassUnitList.add(teachingClassUnit);
        }
        return teachingClassUnitIndex;
    }

    /**
     * 生成排课教学班单元
     * @param taskId 排课计划id
     * @param teachingClassList 需要转换的教学班列表信息
     * @param startPos 教学班排课单元生成的起始位置
     * @return
     */
    public LinkedList<TeachingClassUnit> convertToTeachingClassUnit(Integer taskId,
                                                                       List<ArrangeTeachingClassDto> teachingClassList,
                                                                       Integer startPos) {
        if (teachingClassList != null && !teachingClassList.isEmpty()) {
            //用于存放排课单元数据
            LinkedList<TeachingClassUnit> teachingclassUnitList = new LinkedList<>();
            int teachingClassUnitIndex = startPos;
            for (ArrangeTeachingClassDto teachingClass : teachingClassList) {
                //获取教学班的某个授课类型的周学时数量
                Integer weekNum = teachingClass.getWeekNum();
                //连上节次信息
                Integer connectSection = teachingClass.getConnectSection();
                //连上次数
                Integer connectNumber = teachingClass.getConnectNumber();

                if (connectNumber.equals(0)) {
                    //连上次数为0，默认就按照连上节次是1节，去拆分生成教学班排课单元
                    teachingClassUnitIndex = connectNumberIsZero(teachingClass, weekNum,
                            taskId, teachingClassUnitIndex, teachingclassUnitList);
                }
                else{
                    //过滤掉脏数据，有些教学班没有设置周学时的话，直接跳过
                    if (weekNum != null) {
                        if (weekNum <= connectSection * connectNumber) {
                            //周学时小于等于连上节次，比如周学时是3，连上2节，连上2次，那么相当于两周上6节
                            teachingClassUnitIndex = weekNumEqualOrLessThan(teachingClass, weekNum, connectSection,
                                    taskId, teachingClassUnitIndex, teachingclassUnitList);
                        } else {
                            teachingClassUnitIndex = weekNumMoreThan(teachingClass, weekNum, connectSection,
                                    connectNumber, taskId, teachingClassUnitIndex, teachingclassUnitList);
                        }
                    }
                }
            }

            return teachingclassUnitList;
        }
        log.info("GenerateSchedulingData--convertToTeachingClassUnit: 没有需要转换成教学班单元的教学班信息~！！");
        //不需要排课，返回空
        return null;
    }

    /**
     * 如果周学时，小于等于连上节次*连上次数
     * @param teachingClass 教学班对象
     * @param weekNum 周学时
     * @param connectSection 连上节次
     * @param taskId 计划id
     * @param teachingClassUnitIndex 生成的教学班排课单元id
     * @param teachingclassUnitList 用来存储的教学班排课单元列表信息
     * @return
     */
    private int weekNumEqualOrLessThan(ArrangeTeachingClassDto teachingClass,
                                 Integer weekNum,
                                 Integer connectSection,
                                 Integer taskId,
                                 int teachingClassUnitIndex,
                                 LinkedList<TeachingClassUnit> teachingclassUnitList) {

        //计算每周都需要安排的课程的次数；
        //比如，周课时是3，连上2节，那么每周安排一节（2小节），另外一节，每两周上一次
        int weekArrangeNum = weekNum/connectSection;
        //按照每周的排课数量，生成排课教学班单元，按照连上节次来进行安排
        for (int i = 0; i < weekArrangeNum; i++) {
            //计算教学班排课单元的id
            teachingClassUnitIndex++;
            teachingclassUnitList.add(generateTeachingClassUnit(teachingClass,
                    teachingClassUnitIndex,
                    taskId,
                    teachingClass.getWeek()));
        }
        //每connectSection周，安排otherWeekArrangeNum次课
        //即如果周学时是6，连上节次是4，那么每4周，需要安排2次4节连上的课程；
        int otherWeekArrangeNum = weekNum%connectSection;
        //以连上节次作为周期，如果间隔周次上课的次数大于0
        //那么需要安排每隔连上周数，上otherWeekArrangeNum次课
        if (otherWeekArrangeNum > 0) {
            teachingClassUnitIndex++;
            String weeks = BinaryUtil.swapOneToZero(teachingClass.getWeek(), connectSection, otherWeekArrangeNum);
            teachingclassUnitList.add(generateTeachingClassUnit(teachingClass,
                    teachingClassUnitIndex,
                    taskId,
                    weeks));
        }

        return teachingClassUnitIndex;
    }

    /**
     * 如果周学时，大于连上节次*连上次数，剩余的节数，都按照1节课连上进行处理
     * @param teachingClass 教学班对象
     * @param weekNum 周学时
     * @param connectSection 连上节次
     * @param connectNumber 连上次数
     * @param taskId 计划id
     * @param teachingClassUnitIndex 生成的教学班排课单元id
     * @param teachingclassUnitList 用来存储的教学班排课单元列表信息
     * @return
     */
    private int weekNumMoreThan(ArrangeTeachingClassDto teachingClass,
                                       Integer weekNum,
                                       Integer connectSection,
                                       Integer connectNumber,
                                       Integer taskId,
                                       int teachingClassUnitIndex,
                                       LinkedList<TeachingClassUnit> teachingclassUnitList) {
        //周课时减去连上节次乘以连上次数，
        //得到的就是连上节次是1节的排课单元的数量
        int leaveNum = weekNum - connectSection*connectNumber;
        //按照每周的连上次数，生成排课教学班单元，按照连上节次来进行安排
        for (int i = 0; i < connectNumber; i++) {
            //计算教学班排课单元的id
            teachingClassUnitIndex++;
            //按照连上节次，生成教学班排课单元，将教学班排课单元加入列表
            teachingclassUnitList.add(generateTeachingClassUnit(teachingClass,
                    teachingClassUnitIndex,
                    taskId,
                    teachingClass.getWeek()));
        }

        //先拆成两节连上
        int doubleNumber = leaveNum / 2;
        for (int i = 0; i < doubleNumber; i++) {
            //计算教学班排课单元的id
            teachingClassUnitIndex++;
            //按照连上节次，生成教学班排课单元，将教学班排课单元加入列表
            TeachingClassUnit teachingClassUnit = generateTeachingClassUnit(teachingClass,
                    teachingClassUnitIndex,
                    taskId,
                    teachingClass.getWeek());
            //将1节连上的教学班排课单元的连上节次设为1
            teachingClassUnit.setConnectSection(2);
            //将1节连上的教学班排课单元的连上次数，设为剩余的上课节次
            teachingClassUnit.setConnectNumber(1);
            //将教学班排课单元加入列表
            teachingclassUnitList.add(teachingClassUnit);
        }

        int oneNumber = leaveNum % 2;
        //剩余的上课节次，按照1节连上，生成排课教学班单元，来进行安排
        for (int i = 0; i < oneNumber; i++) {
            //计算教学班排课单元的id
            teachingClassUnitIndex++;
            //按照1节课连上，生成教学班排课单元
            TeachingClassUnit teachingClassUnit = generateTeachingClassUnit(teachingClass,
                    teachingClassUnitIndex,
                    taskId,
                    teachingClass.getWeek());
            //将1节连上的教学班排课单元的连上节次设为1
            teachingClassUnit.setConnectSection(1);
            //将1节连上的教学班排课单元的连上次数，设为剩余的上课节次
            teachingClassUnit.setConnectNumber(1);
            //将教学班排课单元加入列表
            teachingclassUnitList.add(teachingClassUnit);
        }

        return teachingClassUnitIndex;
    }

    /**
     * 生成排课单元信息
     * @param teachingClass 教学班信息
     * @param index 教学班排课单元id
     * @param taskId 学期排课计划id
     * @param weeks 周次信息，二进制字符串
     * @return
     */
    private TeachingClassUnit generateTeachingClassUnit(ArrangeTeachingClassDto teachingClass,
                                                 Integer index,
                                                 Integer taskId,
                                                 String weeks) {
        TeachingClassUnit teachingClassUnit = new TeachingClassUnit();
        //设置排课单元id
        teachingClassUnit.setTeachingClassUnitId(index);
        //设置教学班id
        teachingClassUnit.setTeachingClassId(teachingClass.getTeachingClassId());
        //设置教学班id
        teachingClassUnit.setTeachingClassName(teachingClass.getTeachingClassName());
        //设置教学班类型0:无需选课 1:在线选课 2:分级教学 3:预选教学班
        teachingClassUnit.setTeachingClassType(teachingClass.getTeachingClassType());
        //设置教学班人数
        teachingClassUnit.setTeachingClassSize(teachingClass.getStudentNumber());
        //设置课程任课教室
        teachingClassUnit.setTeacherIds(teachingClass.getTeacherIds());
        //设置课程上课学生id
        teachingClassUnit.setStudentIds(teachingClass.getStudentIds());
        //设置周次
        teachingClassUnit.setClassWeeks(weeks);
        //设置教学班所需教室类型
        teachingClassUnit.setRoomType(teachingClass.getRoomType());
        //设置课程类型，环节/课程
        teachingClassUnit.setCourseType(teachingClass.getFlag());
        //设置授课类型，讲授/实验/上机/其他
        teachingClassUnit.setTeachingType(teachingClass.getHourType());
        //设置排课选课计划id
        teachingClassUnit.setTaskId(taskId);
        //设置禁排的课时信息，多个以逗号分隔
        teachingClassUnit.setProhibitTimeslotCodes(teachingClass.getProhibitTimeslotCodes());
        //设置课程id
        teachingClassUnit.setCourseId(teachingClass.getCourseId());
        teachingClassUnit.setCourseLimit(teachingClass.getCourseLimit());
        //设置课程名称
        teachingClassUnit.setCourseName(teachingClass.getCourseName());
        //设置校区id
        teachingClassUnit.setCampusId(teachingClass.getCampusId());
        //设置连上节次
        teachingClassUnit.setConnectSection(teachingClass.getConnectSection());
        //设置连上次数
        teachingClassUnit.setConnectNumber(teachingClass.getConnectNumber());
        //设置连上分组标识，使用UUID作为分组
        //允许每次都生成，后端只是回根据这个UUID来判断未排的课节数据，是否属于同一排课单元；
        //TODO 这个地方，如果是分布式环境，需要考虑生成UUID相同的情况
        teachingClassUnit.setTimeGroup(UUID.randomUUID().toString());
        //设置尽量排的课时编码信息，多个以逗号分隔
        teachingClassUnit.setFixTimeslotCodes(teachingClass.getFixTimeslotCodes());
        //设置教学班指定需要分配的教室
        teachingClassUnit.setRoomId(teachingClass.getRoomId());
        //设置行政班id
        teachingClassUnit.setPhyClassId(teachingClass.getToClass());
        //分班标识 UUID生成 相同分组的教学班，标识相同，排课时需要分配相同课时的教学班
        teachingClassUnit.setUniqueShiftSign(teachingClass.getUniqueShiftSign());
        //拆班学生分配 1单数 2双数
        teachingClassUnit.setIsSingle(teachingClass.getIsSingle());
        //课程+教师id字符串，用于拆班课程的区分
        //由于有些课程，需要两个教师教，通过这种方式，来区分不同的课程对应的教学班
        teachingClassUnit.setCourseTeacherIDs(teachingClass.getCourseId()+teachingClass.getTeacherIds());
        //设置院系id
        teachingClassUnit.setCollegeId(teachingClass.getCourseCollegeId());


        teachingClassUnit.setSplitClassUuid(teachingClass.getSplitClassUuid());

        teachingClassUnit.setTeacherCourseCount(teachingClass.getTeacherCourseCount());

        return teachingClassUnit;
    }

    /**
     * 判断是否这个教师id，在教师id字符串中，
     * @param teacherIds 教师id字符串，多个以逗号分隔
     * @param teacherId 教师id
     * @return
     */
    protected boolean isTeacherIdExist(String teacherIds, String teacherId) {
        //如果teacherIds为空或者空字符串
        if (StrUtil.isBlank(teacherIds)) {
            //返回不包含
            return false;
        }
        //将字符串进行拆分
        String [] teacherIdArr = teacherIds.split(",");
        for (String splitTeacherId : teacherIdArr) {
            if (splitTeacherId.equals(teacherId)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 初始化算法排课的，不同教室类型上课的教学班信息
     * @param taskId 排课计划id
     * @param teachingClassList 教学班列表
     * @param startPos 教学班排课单元生成的起始位置；
     * @param fixTeachingClassUnitList 尽量排课剩下的，未分配的教学班排课单元
     * @return
     */
    protected Map<Integer, LinkedList<TeachingClassUnit>> getTeachingClassUnitMap(Integer taskId,
                                                                               List<ArrangeTeachingClassDto> teachingClassList,
                                                                               Integer startPos,
                                                                               LinkedList<TeachingClassUnit> fixTeachingClassUnitList) {
        //定义保存转换后的教学班排课单元信息列表
        LinkedList<TeachingClassUnit> retList = new LinkedList<>();
        if (teachingClassList != null && !teachingClassList.isEmpty()) {
            retList.addAll(convertToTeachingClassUnit(taskId, teachingClassList, startPos));
            log.info("GenerateSchedulingData--getTeachingClassUnitMap: 教学班转换为教学班排课单元共{}个", retList.size());
        }

        //将尽量排剩下的未分配的教学班排课单元，加入到自动排课的列表中
        if (fixTeachingClassUnitList != null && !fixTeachingClassUnitList.isEmpty()) {
            log.info("GenerateSchedulingData--getTeachingClassUnitMap: 优先排未处理的教学班排课单元共{}个",
                    (fixTeachingClassUnitList!=null?fixTeachingClassUnitList.size():0));
            retList.addAll(fixTeachingClassUnitList);
        }

        if (!retList.isEmpty()) {
            //按教室类型，将排课数据进行分组
            Map<Integer, LinkedList<TeachingClassUnit>> teachingClassUnitMap = retList
                    .stream()
                    .collect(Collectors.groupingBy(TeachingClassUnit::getRoomType,Collectors.toCollection(LinkedList::new)));

            return teachingClassUnitMap;
        }

        log.info("GenerateSchedulingData--getTeachingClassUnitMap: 没有需要转换成课程单元Map的教学班信息~！！");
        return null;
    }

    /**
     * 初始化算法排课的，不同教室类型上课的教学班信息
     * @param timeslotRoomList
     * @return
     */
    protected Map<Integer, LinkedList<TimeslotRoom>> getTimeslotRoomMap(LinkedList<TimeslotRoom> timeslotRoomList) {

        if (timeslotRoomList != null && !timeslotRoomList.isEmpty()) {
            //按教室类型，将教室课时分组列表数据，进行分组
            Map<Integer, LinkedList<TimeslotRoom>> timeslotRoomMap = timeslotRoomList
                    .stream()
                    .collect(Collectors.groupingBy(TimeslotRoom::getRoomType,Collectors.toCollection(LinkedList::new)));
            return timeslotRoomMap;
        }
        log.info("GenerateSchedulingData--getTimeslotRoomMap: 没有需要转换成教室课时分组Map的分组列表信息~！！");
        return null;
    }

    /**
     * 从source列表中，将needToRemove列表中包含的元素，删除
     * @param source 需要处理的源头列表
     * @param needToRemove 需要从源头列表中，清理掉的列表
     * @return
     */
    protected LinkedList<TimeslotRoom> removeTimeslotRoomList(LinkedList<TimeslotRoom> source,
                                                              LinkedList<TimeslotRoom> needToRemove) {
        if (source != null && !source.isEmpty()
                && needToRemove != null && !needToRemove.isEmpty()) {
            //遍历确定分配的教室课时分组信息列表
            for (TimeslotRoom timeslotRoom : needToRemove) {
                //如果是周次信息完全匹配
                if (timeslotRoom.getIsRemove()) {
                    //从原始数据中，删掉已分配的教室课时分组信息
                    source.remove(timeslotRoom);
                }
            }
        }

        return source;
    }

    /**
     * 将新列表加入到原始列表中，然后进行排序
     * @param source 原始的教室课时分组信息列表
     * @param needToAdd 需要添加的教室课时分组信息列表
     * @return
     */
    protected void addSortTimeslotRoomList(List<TimeslotRoom> source, List<TimeslotRoom> needToAdd) {

        if (CollUtil.isEmpty(needToAdd)){
            return;
        }

        List<TimeslotRoom> mergedList = new ArrayList<>();
        int i = 0, j = 0;

        // 遍历两个列表
        while (i < source.size() && j < needToAdd.size()) {
            TimeslotRoom item1 = source.get(i);
            TimeslotRoom item2 = needToAdd.get(j);

            // 比较a字段大小


            if (item1.compareTo(item2) < 0) {
                mergedList.add(item1);
                i++;
            } else {
                mergedList.add(item2);
                j++;
            }
        }

        // 将剩余元素添加到合并后的列表中
        while (i < source.size()) {
            mergedList.add(source.get(i));
            i++;
        }

        while (j < needToAdd.size()) {
            mergedList.add(needToAdd.get(j));
            j++;
        }

        // 清空原始列表
        source.clear();
        // 将合并后的列表添加到原始列表中
        source.addAll(mergedList);

    }
}
